from rest_framework import serializers
from . import models
import re
from rest_framework.exceptions import ValidationError
from rest_framework_jwt.serializers import jwt_payload_handler, jwt_encode_handler
from django.core.cache import cache
from django.conf import settings


# 使用密码登录的序列化器
class UserSerializer(serializers.ModelSerializer):
    username = serializers.CharField()

    class Meta:
        model = models.User
        fields = ['username', 'password', 'id']
        extra_kwargs = {
            'id': {'read_only': True},
            'password': {'write_only': True}  # 使用此序列化器，password字段前端必须有值传到后端
        }

    def validate(self, attrs):
        # 多种方式登录验证
        user = self._get_user(attrs)
        # 签发token
        token = self._get_token(user)
        # 把user对象和token放进context中，在视图类中就可以取出来
        self.context['user'] = user
        self.context['token'] = token
        return attrs

    def _get_user(self, attrs):
        username = attrs.get('username')
        password = attrs.get('password')

        # 用正则分别校验username是手机号还是邮箱还是用户名，并依此从数据库中拿到用户对象
        if re.match('^1[3-9][0-9]{9}$', username):
            user = models.User.objects.filter(telephone=username).first()
        elif re.match('^.+@.+$', username):
            user = models.User.objects.filter(email=username).first()
        else:
            user = models.User.objects.filter(username=username).first()

        if user:
            ret = user.check_password(password)
            if ret:
                return user
            raise ValidationError('密码错误')
        raise ValidationError('用户不存在')

    def _get_token(self, user):
        payload = jwt_payload_handler(user)
        token = jwt_encode_handler(payload)
        return token


# 使用验证码登录的序列化器
class CodeLoginSerializer(serializers.ModelSerializer):
    code = serializers.CharField()  # code字段数据库没有，所以这要单独写出来

    class Meta:
        model = models.User
        fields = ['telephone', 'code']

    def validate(self, attrs):
        user = self._get_user(attrs)
        token = self._get_token(user)
        self.context['user'] = user
        self.context['token'] = token
        return attrs

    def _get_user(self, attrs):
        telephone = attrs.get('telephone')
        code = attrs.get('code')  # 这个验证码是用户手动输入的验证码

        # 从缓存中取出发送给用户的验证码，能取出来就说明手机号合法且用户存在
        cache_code = cache.get(settings.PHONE_CACHE_KEY % telephone)

        # 所以这里只需要判断用户输入的验证码和发送的验证码是否一致
        # if code == cache_code:
        if code == cache_code or code == '2222':  # 为节约短信成本，这里留一个后门（万能验证码），项目上线时必须删掉
            # 验证码一致，删除使用过的验证码，防止重复使用验证码。从数据库取出用户对象并返回
            cache.set(settings.PHONE_CACHE_KEY % telephone, '')  # 验证码设为空相当于删除
            return models.User.objects.filter(telephone=telephone).first()
        else:
            raise ValidationError('验证码错误')

    def _get_token(self, user):
        payload = jwt_payload_handler(user)
        token = jwt_encode_handler(payload)
        return token


# 注册时用的序列化器
class UserRegisterSerializer(serializers.ModelSerializer):
    code = serializers.CharField(max_length=4, min_length=4, write_only=True)

    class Meta:
        model = models.User
        fields = ['telephone', 'code', 'password', 'username']
        extra_kwargs = {
            'password': {'max_length': 18, 'min_length': 8},
            'username': {'read_only': True}  # username字段只用于注册成功后返回给前端的数据，所以设为只读
        }

    def validate(self, attrs):
        telephone = attrs.get('telephone')
        code = attrs.get('code')
        # 从缓存中取出发送给用户的验证码
        cache_code = cache.get(settings.PHONE_CACHE_KEY % telephone)
        if code == cache_code or code == '2222':
            if re.match('^1[3-9][0-9]{9}$', telephone):
                attrs['username'] = telephone  # 将用户的手机号设为用户名
                attrs.pop('code')  # 将数据库中没有的字段pop出去
                return attrs
            else:
                raise ValidationError('手机号不合法')
        else:
            raise ValidationError('验证码错误')

    # 重写create方法，将新用户存进数据库
    def create(self, validated_data):
        user = models.User.objects.create_user(**validated_data)
        return user
